/***************************************************************************
   Filename:    Game.cpp
   Author:      Josh Partridge
   Description: Contains actual breakout game (Game class)
****************************************************************************/

#include "Game.h"

using namespace std;
/*
    Class: Game
    Description: This is what actually handles the gameplay.
                 It has its own game loop, and instead of passing
                 a reference to the statemachine and brush like the instructions
                 or scoreboard classes, we store the referances in a pointer.
                 This means that game basically shares the breakout.cpp 's statemachine
                 and brush.
                 Note: our brush and stateMachine are actually pointers, so
                 instead of using brush.method() do brush -> method()
                 The same goes for stateMachine.
*/

/************************************************************************************
 Name:               Game - constructor
 Description:        initializes Game's attributes
 Input:
              state: pointer to main's StateMachine
           theBrush: pointer to main's Brush
*************************************************************************************/
Game::Game(StateMachine *state, Brush *theBrush)
{
    stateMachine = state;
    brush = theBrush;
    lives = 3;
    level = 1;
    score = 0;
    prevScore = 0;

    ball = Ball(&score);
    updatePaddle=false;
    returning = false;
    finalLevel = 5;
}

/************************************************************************************
 Name:               levelIntro
 Description:        displays level # or provides intro to boss level(s)
*************************************************************************************/
void Game::levelIntro()
{
    brush -> clear();

    if(level>finalLevel)
    {
        returning = false;

        brush -> setColor(white);
        brush -> move(55, 16);
        cout << "Congradulations";
        brush -> move(57, 19);
        cout << "YOU WIN!!!";
        Sleep(500);
        brush -> setColor(blue);
        brush -> move(57, 19);
        cout << "YOU WIN!!!";
        Sleep(500);
        brush -> setColor(red);
        brush -> move(57, 19);
        cout << "YOU WIN!!!";
        Sleep(500);
        brush -> setColor(green);
        brush -> move(57, 19);
        cout << "YOU WIN!!!";
        Sleep(500);
        level = 1;
        //input name
        saveScore();
    }
    else
    {
        //show points gained from previous level.
        tallyScore();
        brush -> clear();

             //not a boss level
        if(level%5!=0)
        {
            brush -> setColor(white);
            brush -> move(57, 17);
            cout << "Level " << level;
            Sleep(1500);
        }
    }

}

/************************************************************************************
 Name:               generateBrick
 Description:        sets health and color for a brick (semi random)
 Input:
              brick: reference of the brick to be modified
*************************************************************************************/
void Game::generateBrick(Brick& brick)
{

    //health is 1 or 2, weighted by level #
    int health = 1;
    if( level >= rand() % 5 + 1 )
    {
        health = 2;
    }
    brick.setHealth(health);

    if( level == 1 )
    {
        switch( rand() % 3)
        {
            case 0: brick.setColor(red); break;
            case 1: brick.setColor(darkred); break;
            case 2: brick.setColor(yellow); break;
        }
    }
    else if (level==2)
    {
        switch( rand() % 4)
        {
            case 0: brick.setColor(blue); break;
            case 1: brick.setColor(darkcyan); break;
            case 2: brick.setColor(darkblue); break;
            case 3: brick.setColor(cyan); break;
        }
    }
    else if (level==3)
    {
        switch( rand() % 3)
        {
            case 0: brick.setColor(darkyellow); break;
            case 1: brick.setColor(green); break;
            case 2: brick.setColor(darkgreen); break;
        }
    }
    else if (level==4)
    {
        switch( rand() % 3)
        {
            case 0: brick.setColor(lightgrey); break;
            case 1: brick.setColor(grey); break;
            case 2: brick.setColor(white); break;
        }
    }
    else if (level==5)
    {
        switch( rand() % 3)
        {
            case 0: brick.setColor(red); break;
            case 1: brick.setColor(blue); break;
            case 2: brick.setColor(green); break;
        }
    }

}

/************************************************************************************
 Name:               generateLevel
 Description:        Populates the bricks vector depending on level number and a degree
                     of chance (weighted random numbers).
*************************************************************************************/
void Game::generateLevel()
{
    srand ( time(NULL) );

    //empty the vector of bricks so we can start filling it
    bricks.clear();

    int x = 10;
    int y = 8;

    if(level < 5)
    {
        for(int i = 0; i < 20; i++)
        {
            //make a brick and set bounds
            Brick brick(x,y);
            //right now this just randomizes the health and color
            generateBrick(brick);
            //add to the vector
            bricks.push_back(brick);

            x += 5;
        }
    }
    if (level > 1)
    {
        x = 7;
        y = 10;
        for(int i = 0; i < 21; i++)
        {
            //make a brick and set bounds
            Brick brick(x,y);
            //right now this just randomizes the health
            generateBrick(brick);
            //add to the vector
            bricks.push_back(brick);
            x += 5;
        }
    }
    if (level > 2)
    {
        x = 10;
        y = 12;
        for(int i = 0; i < 20; i++)
        {
            //make a brick and set bounds
            Brick brick(x,y);
            //right now this just randomizes the health
            generateBrick(brick);
            //add to the vector
            bricks.push_back(brick);
            x += 5;
        }
    }
    if (level > 3)
    {
        x = 7;
        y = 6;
        for(int i = 0; i < 21; i++)
        {
            //make a brick and set bounds
            Brick brick(x,y);
            //right now this just randomizes the health
            generateBrick(brick);
            //add to the vector
            bricks.push_back(brick);
            x += 5;
        }
    }
    if (level > 4)
    {
        x = 10;
        y = 4;
        for(int i = 0; i < 20; i++)
        {
            //make a brick and set bounds
            Brick brick(x,y);
            //right now this just randomizes the health
            generateBrick(brick);
            //add to the vector
            bricks.push_back(brick);
            x += 5;
        }
    }
}

/************************************************************************************
 Name:               drawLevel
 Description:        called only at the beginning of Game.run(), this draws the bricks
                     to the screen.
*************************************************************************************/
void Game::drawLevel()
{
    for(vector<Brick>::iterator it = bricks.begin(); it != bricks.end(); ++it)
    {
        it -> display(*brush);
    }
}

/************************************************************************************
 Name:               drawScore
 Description:        Displays current score in bottom right corner of screen
*************************************************************************************/
void Game::drawScore()
{
    brush -> setColor(cyan);
    brush -> move(90, 38);
    cout << "Score: ";
    brush -> setColor(white);
    cout << score;
}

/************************************************************************************
 Name:               saveScore
 Description:        Displays scoreboard with player's score inserted if player places
                     in the top ten. Also calls getPlayerName if this is the case.
*************************************************************************************/
void Game::saveScore()
{

    std::ifstream in ("highscores.txt");
        if(!in)
            return;

    string name;
    long double score_in;

    //we need valid values to shift everything down one
    string names[]={"","","","","","","","","",""};
    long double scores[]={0,0,0,0,0,0,0,0,0,0};

    int place = 0;
    int playerPlace=11;

    //get all of the names and scores in the file
    while(in>>name && place < 10)
    {
        names[place] = name;
        in >> score_in;
        scores[place] = score_in;
        place++;
    }

    //cycle towards first place
    for (int i=place; i > 0; i--)
    {
        //stop looping if the player doesn't place in the top 10
        if(score<=scores[9]){
            i=-1;
        }
        //if player gets first place or we find a score greater (not last place though)
        else if (i==1 || score <= scores[i-2])
        {

            //move every score (lower than the player's) down one place
            for (int k = 9; k > i-1; k--)
            {
                scores[k] = scores[k-1];
                names[k] = names[k-1];

            }

            //insert the player's score into the list
            scores[i-1] = score;
            //store the player's place in a variable outside the loop's scope
            playerPlace = i;
            //get out of the loop without using break statement
            i = -1;
        }
    }

    //output the high score list
    brush -> clear();

    brush -> setColor(blue);

    if(playerPlace<=10)
        brush -> setColor(yellow);

    // border function found in helpers
    singleLineBox(*brush, 3, 2, 113, 35);

    brush -> setColor(cyan);
    brush -> move(56, 1);
    cout << "HIGH SCORES";

    place = 1;
    while(scores[place-1] > 0 && place <= 10)
    {
        brush -> move(45, 3 + 3*place);
        cout << place;
        brush -> move(52, 3 + 3*place);
        cout << names[place-1];
        brush -> move(67, 3 + 3*place);
        cout << fixed << setprecision(0) << scores[place-1];
        place++;
        brush -> setColor(white);
    }

    //if the player placed in the top 10
    if(playerPlace<=10)
    {
        names[playerPlace-1] = getPlayerName(playerPlace);
        ofstream out;
        out.open("highscores.txt");
        for(int i=0; i<10; i++)
        {
            out << names[i] << " " << scores[i] << "\n";
        }
    }
    else
    {
        brush -> setColor(red);
        brush -> move(52, 36);
        cout << "Your score:    " << score;
    }

    //so that it doesn't automatically display the scoreboard again when player chooses new game.
    score = 0;
    prevScore = 0;

    Sleep(1500);
    brush -> clearInput();

    brush -> setColor(white);
    brush -> move (48, 38);

    cout << "Press any key to continue";


    getch();
}

void Game::tallyScore()
{
    if(level > 1)
        {
        brush -> setColor (blue);
        singleLineBox(*brush, 40, 10, 40, 16);

        brush -> setColor (cyan);
        brush -> move(44, 12);
        cout << "Score so far: ";
        brush -> setColor (white);
        cout << prevScore;

        brush -> setColor (cyan);
        brush -> move (44, 15);
        cout << "Points from level " << level - 1 << ": ";
        brush -> setColor (white);
        cout << score - prevScore;

        brush -> setColor (cyan);
        brush -> move (44, 18);
        cout << "lives multiplier: ";
        brush -> setColor (white);
        cout << "x" << lives;

        brush -> setColor (cyan);
        brush -> move (44, 21);
        cout << "Total points awarded: ";
        brush -> setColor (white);
        cout << "+" << (score - prevScore)*lives;

        score = prevScore + (score-prevScore)*lives;
        prevScore = score;

        brush -> setColor (cyan);
        brush -> move (44, 24);
        cout << "Current score: ";
        brush -> setColor (white);
        cout << score;

        Sleep(2000);
        brush -> clearInput();

        brush -> move (48, 30);
        cout << "Press any key to continue";

        getch();
    }

    //story text
    switch(level)
    {
        case 1:
                brush -> clear();
                brush -> setColor (blue);
                singleLineBox(*brush, 38, 10, 51, 11);
                brush -> setColor (white);
                brush -> move(40, 12);
                cout << "Greetings Brickfighter!"; Sleep(1000);
                brush -> move(40, 14);
                cout << "You have been recruited by the Brick League";
                brush -> move(40, 15);
                cout << "to defend the frontier against the Brick Armada."; Sleep(2000);
                brush -> move(40, 17);
                cout << "Are you ready?";
                Sleep(1000);
                brush -> move(40, 19);
                cout << "Prepare for blast off!";
                break;

        case 2:
                brush -> clear();
                brush -> setColor (blue);
                singleLineBox(*brush, 34, 15, 53, 4);
                brush -> setColor (white);
                brush -> move(36, 17);
                cout << "Alright, User.  Let's see how you do at this game!";
                break;

        case 3:
                brush -> clear();
                brush -> setColor (blue);
                singleLineBox(*brush, 44, 10, 35, 9);
                brush -> setColor (white);
                brush -> move(46, 12);
                cout << "If my calculations are correct,";
                brush -> move(46, 14);
                cout << "when this paddle hits 88 mph..."; Sleep(1000);
                brush -> move(46, 16);
                cout << "It's going to keep breaking";
                brush -> move(46, 17);
                cout << "bricks the same way.";
                break;

        case 4:
                brush -> clear();
                brush -> setColor (blue);
                singleLineBox(*brush, 48, 17, 23, 4);
                brush -> setColor (white);
                brush -> move(50, 19);
                cout << "It's full of bricks!";
                break;

        case 5:
                brush -> clear();
                brush -> setColor (blue);
                singleLineBox(*brush, 42, 10, 35, 13);
                brush -> setColor (white);
                brush -> move(44, 12);
                cout << "You're almost there."; Sleep(1000);
                brush -> move(44, 14);
                cout << "Try to land on that small moon.";
                brush -> move(44, 16);
                cout << "Wait, that's no moon,";
                brush -> move(44, 18);
                cout << "That's a SPACEBRICK!"; Sleep(1000);
                brush -> move(44, 21);
                cout << "May the Force be with you...";
                break;


        default: break;
    }
    Sleep(1000);
    brush -> clearInput();

    brush -> move (48, 30);
    cout << "Press any key to continue";

    getch();

}

/************************************************************************************
 Name:               getLives
 Description:        getter for lives
 Output:
             return: int representing number of lives remaining
*************************************************************************************/
int Game::getLives()
{
    return lives;
}

/************************************************************************************
 Name:               getPlayerName
 Description:        accepts an 8 letter name to insert into the scoreboard
 Input:
              place: player's place on the scoreboard(1 - 10)
 Output:
             string: the final name
*************************************************************************************/
string Game::getPlayerName(int place)
{

    string name = "        ";

    brush -> move(52, 3 + 3*place);
    cout << "________";
    int spot =0;
    char letter;

    //loop will execute at least once; needs ENTER to continue
    do
    {
        for(int i= 0; i<8; i++)
        {
            if(name[i]==' ')
                brush -> plot(52+i, 3 + 3*place, '_');
            else
                brush -> plot(52+i, 3 + 3*place, name[i]);
        }
        letter=getInput();
        if(spot<8 && letter!=AR_LEFT && letter != AR_RIGHT
                  && letter!=AR_UP && letter != AR_DOWN
                  && ((letter>='A' && letter<='Z') || (letter>='a' && letter<='z')))
        {
            if(letter>='a' && letter<='z')
                letter-=32;

            name[spot]=letter;

            if(spot<8)
                spot++;
        }
        if(letter == BACKSPACE)
        {
            if(spot>0)
                --spot;
            name[spot]=' ';
        }

    }while(letter != ENTER || name=="        ");

    for(int i= 0; i<8; i++)
    {
        brush -> plot(52+i, 3 + 3*place, name[i]);
    }

    return name;
}

/************************************************************************************
 Name:               display
 Description:        Redraws the paddle and ball, and debug information (ball angle
                     and current score if left CTRL is held down.
*************************************************************************************/
void Game::display()
{
    if(GetAsyncKeyState(VK_LCONTROL))
    {
        drawScore();
        brush -> move(20, 38);
        cout << ball.getTheta() << (unsigned char)248;
        if(ball.getTheta()<100)
            brush->plot(23, 38, ' ');
        if(ball.getTheta()<10)
            brush->plot(22, 38, ' ');
    }

    paddle.redraw(*brush);
    ball.draw(*brush);

}

/************************************************************************************
 Name:               run
 Description:        contains game loop, resets level & displays level # if new level
*************************************************************************************/
void Game::run()
{
    if(stateMachine -> getState() == NEW_GAME || !returning || score<=0)
    {
        if(stateMachine -> getState() == NEW_GAME)
        {
            if(score>0)
                saveScore();
            //reset everything
            lives = 3;
            level = 1;
            score = 0;
            prevScore = 0;
        }
        levelIntro();
        generateLevel();
        paddle.setPosition(50);
        paddle.setVelocity(0);
        paddle.setAcc(0);
        ball.setMagnitude(0);
        //not just unpausing, but starting a new level
        returning = false;
    }

    if(level<=finalLevel)
        stateMachine->setState(IN_GAME);

    brush -> clear();
    brush -> setColor(white);
    singleLineBox(*brush, 3, 2, 113, 35);
    brush -> setColor(cyan);
    brush -> move (75, 38);
    cout <<"Lives: ";
    brush -> setColor(white);
    cout<<lives;
    //draw bricks now. Only damaged blocks will be redrawn.
    //until the next level, we should have no reason to redraw then except to change their color etc.
    drawLevel();
    paddle.draw(*brush, true);

    while ( stateMachine->getState() == IN_GAME )
    {

        //update velocities and position of ball
        //returns true if falls below 37 y position
        if(ball.move(bricks, paddle.getPosition(), paddle.getVelocity(), *brush))
        {
            lives--;
            Sleep(1000);
            if(lives<1)
            {
                saveScore();
                stateMachine->setState(GAME_OVER);
            }
            else
            {
                brush -> clearBox(paddle.getPosition(), 33, 17, 1);
                paddle.setPosition(50);
                paddle.draw(*brush, true);
                paddle.setVelocity(0);
                paddle.setAcc(0);
                ball.setMagnitude(0);
                ball.setTheta(135);
                brush -> setColor(white);
                brush -> move (82, 38);
                cout<<lives;
            }
        }
        else if(bricks.size()==0)
        {
            //reload everything with the next level
            level++;
            generateLevel();
            returning = false;
            stateMachine->setState(CONTINUE);
        }
        //generally just erase & redraw ball, erase & redraw paddle if moved, and redraw score counter
        display();

        Sleep(1000 / FPS);

        ball.erase(*brush);

        //check arrows and escape key
        handleInput();

    }

}

/************************************************************************************
 Name:               handleInput
 Description:        Sets paddle acceleration and allows user to return to menu with ESC
*************************************************************************************/
void Game::handleInput()
{
    //while in game we're going to use the keystate for arrows instead of getch()'ing keys in one at a time
    if(GetAsyncKeyState(VK_LEFT) && !GetAsyncKeyState(VK_RIGHT))
    {
         paddle.setAcc(-1);
         updatePaddle=true;
    }
    else if(!GetAsyncKeyState(VK_LEFT) && GetAsyncKeyState(VK_RIGHT))
    {
        paddle.setAcc(1);
        updatePaddle=true;
    }
    else if(!GetAsyncKeyState(VK_LEFT) && !GetAsyncKeyState(VK_RIGHT))
    {
        paddle.setAcc(0);
        updatePaddle=true;
    }
    else if(updatePaddle)
    {
        paddle.draw(*brush);
        updatePaddle=false;
    }
    //cheat code to skip level
    if(GetAsyncKeyState(VK_DOWN) && GetAsyncKeyState(VK_SHIFT) && GetAsyncKeyState(VK_SPACE))
        bricks.clear();
    //ESC or 'X'
    if(_kbhit())
    {
        if(getInput() == ESC)
        {
            returning = true;
            stateMachine->setState(MAIN_MENU);
        }
    }
}
